﻿namespace FastColoredTextBoxNS
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Windows.Forms;

    /// <summary>
    /// 	Collection of Hints.
    /// 	This is temporary buffer for currently displayed hints.
    /// </summary>
    public class Hints : ICollection<Hint>, IDisposable
    {
        private readonly FastColoredTextBox tb;

        private readonly List<Hint> items = new List<Hint>();

        public Hints(FastColoredTextBox tb)
        {
            this.tb = tb;
            tb.TextChanged += this.OnTextBoxTextChanged;
            tb.KeyDown += this.OnTextBoxKeyDown;
            tb.VisibleRangeChanged += this.OnTextBoxVisibleRangeChanged;
        }

        protected virtual void OnTextBoxKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            if (e.KeyCode == System.Windows.Forms.Keys.Escape && e.Modifiers == System.Windows.Forms.Keys.None)
            {
                this.Clear();
            }
        }

        protected virtual void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
        {
            this.Clear();
        }

        public void Dispose()
        {
            this.tb.TextChanged -= this.OnTextBoxTextChanged;
            this.tb.KeyDown -= this.OnTextBoxKeyDown;
            this.tb.VisibleRangeChanged -= this.OnTextBoxVisibleRangeChanged;
        }

        private void OnTextBoxVisibleRangeChanged(object sender, EventArgs e)
        {
            if (this.items.Count == 0)
            {
                return;
            }

            this.tb.NeedRecalc(true);
            foreach (var item in this.items)
            {
                this.LayoutHint(item);
                item.HostPanel.Invalidate();
            }
        }

        private void LayoutHint(Hint hint)
        {
            if (hint.Inline || hint.Range.Start.iLine >= this.tb.LinesCount - 1)
            {
                if (hint.Range.Start.iLine < this.tb.LineInfos.Count - 1)
                {
                    hint.HostPanel.Top = this.tb.LineInfos[hint.Range.Start.iLine + 1].startY - hint.TopPadding
                                         - hint.HostPanel.Height - this.tb.VerticalScroll.Value;
                }
                else
                {
                    hint.HostPanel.Top = this.tb.TextHeight + this.tb.Paddings.Top - hint.HostPanel.Height
                                         - this.tb.VerticalScroll.Value;
                }
            }
            else
            {
                hint.HostPanel.Top = this.tb.LineInfos[hint.Range.Start.iLine + 1].startY - this.tb.VerticalScroll.Value;
            }

            if (hint.Dock == DockStyle.Fill)
            {
                hint.Width = this.tb.ClientSize.Width - this.tb.LeftIndent - 2;
                hint.HostPanel.Left = this.tb.LeftIndent;
            }
            else
            {
                Point p1 = this.tb.PlaceToPoint(hint.Range.Start);
                Point p2 = this.tb.PlaceToPoint(hint.Range.End);
                int cx = (p1.X + p2.X) / 2;
                hint.HostPanel.Left = Math.Max(this.tb.LeftIndent, cx - hint.HostPanel.Width / 2);
            }
        }

        public IEnumerator<Hint> GetEnumerator()
        {
            foreach (var item in this.items)
            {
                yield return item;
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        /// <summary>
        /// 	Clears all displayed hints
        /// </summary>
        public void Clear()
        {
            this.items.Clear();
            if (this.tb.Controls.Count != 0)
            {
                var toDelete = new List<Control>();
                foreach (Control item in this.tb.Controls)
                {
                    if (item is UnfocusablePanel)
                    {
                        toDelete.Add(item);
                    }
                }

                foreach (var item in toDelete)
                {
                    this.tb.Controls.Remove(item);
                }

                for (int i = 0; i < this.tb.LineInfos.Count; i++)
                {
                    LineInfo li = this.tb.LineInfos[i];
                    li.bottomPadding = 0;
                    this.tb.LineInfos[i] = li;
                }
                this.tb.NeedRecalc();
                this.tb.Invalidate();
                this.tb.Select();
                this.tb.ActiveControl = null;
            }
        }

        /// <summary>
        /// 	Add and shows the hint
        /// </summary>
        /// <param name="hint"> </param>
        public void Add(Hint hint)
        {
            this.items.Add(hint);

            if (hint.Inline || hint.Range.Start.iLine >= this.tb.LinesCount - 1)
            {
                LineInfo li = this.tb.LineInfos[hint.Range.Start.iLine];
                hint.TopPadding = li.bottomPadding;
                li.bottomPadding += hint.HostPanel.Height;
                this.tb.LineInfos[hint.Range.Start.iLine] = li;
                this.tb.NeedRecalc(true);
            }

            this.LayoutHint(hint);

            this.tb.OnVisibleRangeChanged();

            hint.HostPanel.Parent = this.tb;

            this.tb.Select();
            this.tb.ActiveControl = null;
            this.tb.Invalidate();
        }

        /// <summary>
        /// 	Is collection contains the hint?
        /// </summary>
        public bool Contains(Hint item)
        {
            return this.items.Contains(item);
        }

        public void CopyTo(Hint[] array, int arrayIndex)
        {
            this.items.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// 	Count of hints
        /// </summary>
        public int Count
        {
            get
            {
                return this.items.Count;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public bool Remove(Hint item)
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// 	Hint of FastColoredTextbox
    /// </summary>
    public class Hint
    {
        /// <summary>
        /// 	Text of simple hint
        /// </summary>
        public string Text
        {
            get
            {
                return this.HostPanel.Text;
            }
            set
            {
                this.HostPanel.Text = value;
            }
        }

        /// <summary>
        /// 	Linked range
        /// </summary>
        public Range Range { get; set; }

        /// <summary>
        /// 	Backcolor
        /// </summary>
        public Color BackColor
        {
            get
            {
                return this.HostPanel.BackColor;
            }
            set
            {
                this.HostPanel.BackColor = value;
            }
        }

        /// <summary>
        /// 	Second backcolor
        /// </summary>
        public Color BackColor2
        {
            get
            {
                return this.HostPanel.BackColor2;
            }
            set
            {
                this.HostPanel.BackColor2 = value;
            }
        }

        /// <summary>
        /// 	Border color
        /// </summary>
        public Color BorderColor
        {
            get
            {
                return this.HostPanel.BorderColor;
            }
            set
            {
                this.HostPanel.BorderColor = value;
            }
        }

        /// <summary>
        /// 	Fore color
        /// </summary>
        public Color ForeColor
        {
            get
            {
                return this.HostPanel.ForeColor;
            }
            set
            {
                this.HostPanel.ForeColor = value;
            }
        }

        /// <summary>
        /// 	Text alignment
        /// </summary>
        public StringAlignment TextAlignment
        {
            get
            {
                return this.HostPanel.TextAlignment;
            }
            set
            {
                this.HostPanel.TextAlignment = value;
            }
        }

        /// <summary>
        /// 	Font
        /// </summary>
        public Font Font
        {
            get
            {
                return this.HostPanel.Font;
            }
            set
            {
                this.HostPanel.Font = value;
            }
        }

        /// <summary>
        /// 	Occurs when user click on simple hint
        /// </summary>
        public event EventHandler Click
        {
            add
            {
                this.HostPanel.Click += value;
            }
            remove
            {
                this.HostPanel.Click -= value;
            }
        }

        /// <summary>
        /// 	Inner control
        /// </summary>
        public Control InnerControl { get; set; }

        /// <summary>
        /// 	Docking (allows None and Fill only)
        /// </summary>
        public DockStyle Dock { get; set; }

        /// <summary>
        /// 	Width of hint (if Dock is None)
        /// </summary>
        public int Width
        {
            get
            {
                return this.HostPanel.Width;
            }
            set
            {
                this.HostPanel.Width = value;
            }
        }

        /// <summary>
        /// 	Height of hint
        /// </summary>
        public int Height
        {
            get
            {
                return this.HostPanel.Height;
            }
            set
            {
                this.HostPanel.Height = value;
            }
        }

        /// <summary>
        /// 	Host panel
        /// </summary>
        public UnfocusablePanel HostPanel { get; private set; }

        internal int TopPadding { get; set; }

        /// <summary>
        /// 	Tag
        /// </summary>
        public object Tag { get; set; }

        /// <summary>
        /// 	Cursor
        /// </summary>
        public Cursor Cursor
        {
            get
            {
                return this.HostPanel.Cursor;
            }
            set
            {
                this.HostPanel.Cursor = value;
            }
        }

        /// <summary>
        /// 	Inlining. If True then hint will moves apart text.
        /// </summary>
        public bool Inline { get; set; }

        /// <summary>
        /// 	Scroll textbox to the hint
        /// </summary>
        public virtual void DoVisible()
        {
            this.Range.tb.DoRangeVisible(this.Range, true);
            this.Range.tb.Invalidate();
        }

        private Hint(Range range, Control innerControl, string text, bool inline, bool dock)
        {
            this.Range = range;
            this.Inline = inline;
            this.InnerControl = innerControl;

            this.Init();

            this.Dock = dock ? DockStyle.Fill : DockStyle.None;
            this.Text = text;
        }

        /// <summary>
        /// 	Creates Hint
        /// </summary>
        /// <param name="range"> Linked range </param>
        /// <param name="text"> Text for simple hint </param>
        /// <param name="inline"> Inlining. If True then hint will moves apart text </param>
        /// <param name="dock"> Docking. If True then hint will fill whole line </param>
        public Hint(Range range, string text, bool inline, bool dock)
            : this(range, null, text, inline, dock)
        {
        }

        /// <summary>
        /// 	Creates Hint
        /// </summary>
        /// <param name="range"> Linked range </param>
        /// <param name="text"> Text for simple hint </param>
        public Hint(Range range, string text)
            : this(range, null, text, true, true)
        {
        }

        /// <summary>
        /// 	Creates Hint
        /// </summary>
        /// <param name="range"> Linked range </param>
        /// <param name="innerControl"> Inner control </param>
        /// <param name="inline"> Inlining. If True then hint will moves apart text </param>
        /// <param name="dock"> Docking. If True then hint will fill whole line </param>
        public Hint(Range range, Control innerControl, bool inline, bool dock)
            : this(range, innerControl, null, inline, dock)
        {
        }

        /// <summary>
        /// 	Creates Hint
        /// </summary>
        /// <param name="range"> Linked range </param>
        /// <param name="innerControl"> Inner control </param>
        public Hint(Range range, Control innerControl)
            : this(range, innerControl, null, true, true)
        {
        }

        protected virtual void Init()
        {
            this.HostPanel = new UnfocusablePanel();
            this.HostPanel.Click += this.OnClick;

            if (this.InnerControl != null)
            {
                this.HostPanel.Controls.Add(this.InnerControl);
                this.HostPanel.Width = this.InnerControl.Width + 2;
                this.HostPanel.Height = this.InnerControl.Height + 2;
                this.InnerControl.Dock = DockStyle.Fill;
                this.InnerControl.Visible = true;
                this.BackColor = SystemColors.Control;
            }
            else
            {
                this.HostPanel.Height = this.Range.tb.CharHeight + 5;
            }

            this.Cursor = Cursors.Default;
            this.BorderColor = Color.Silver;
            this.BackColor2 = Color.White;
            this.BackColor = this.InnerControl == null ? Color.Silver : SystemColors.Control;
            this.ForeColor = Color.Black;
            this.TextAlignment = StringAlignment.Near;
            this.Font = this.Range.tb.Parent == null ? this.Range.tb.Font : this.Range.tb.Parent.Font;
        }

        protected virtual void OnClick(object sender, EventArgs e)
        {
            this.Range.tb.OnHintClick(this);
        }
    }
}